﻿using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;

namespace System
{
    /// <summary>
    /// 包含了与字符串正则表达式验证相关的一些常用扩展方法
    /// </summary>
    public static partial class StringExtensions
    {
        #region Match 匹配

        /// <summary>
        /// 指示所指定的正则表达式是否使用指定的匹配选项在指定的输入字符串中找到了匹配项，匹配返回true
        /// </summary>
        /// <param name="input">输入字符串</param>
        /// <param name="pattern">模式字符串</param>
        public static bool IsMatch(this string input, string pattern)
        {
            return IsMatch(input, pattern, RegexOptions.IgnoreCase);
        }

        /// <summary>
        /// 指示所指定的正则表达式是否使用指定的匹配选项在指定的输入字符串中找到了匹配项，匹配返回true
        /// </summary>
        /// <param name="input">输入的字符串</param>
        /// <param name="pattern">模式字符串</param>
        /// <param name="options">筛选条件</param>
        public static bool IsMatch(this string input, string pattern, RegexOptions options)
        {
            return Regex.IsMatch(input, pattern, options);
        }

        /// <summary>
        /// 指定的正则表达式的第一个匹配项的字符串
        /// </summary>
        /// <param name="s"></param>
        /// <param name="pattern"></param>
        /// <returns></returns>
        public static string Match(this string s, string pattern)
        {
            return Match(s, pattern, RegexOptions.IgnoreCase);
        }

        /// 指定的正则表达式的第一个匹配项的字符串
        /// </summary>
        /// <param name="s"></param>
        /// <param name="pattern"></param>
        /// <returns></returns>
        public static string Match(this string s, string pattern, RegexOptions options)
        {
            if (s == null) return "";
            return Regex.Match(s, pattern,options).Value;
        }
        #endregion Match 匹配

        #region 判断

        #region 判断字符串是否是邮箱地址

        /// <summary>
        /// 判断字符串是否是邮箱地址
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static bool IsEmail(this string source)
        {
            if (source.IsNullOrEmpty()) return false;
            return Regex.IsMatch(source, @"^[A-Za-z0-9](([_\.\-]?[a-zA-Z0-9]+)*)@([A-Za-z0-9]+)(([\.\-]?[a-zA-Z0-9]+)*)\.([A-Za-z]{2,})$", RegexOptions.IgnoreCase);
        }

        #endregion 判断字符串是否是邮箱地址

        #region 判断字符串是否包含邮箱地址

        /// <summary>
        /// 判断字符串是否包含邮箱地址
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static bool HasEmail(this string source)
        {
            if (source.IsNullOrEmpty()) return false;
            return Regex.IsMatch(source, @"[A-Za-z0-9](([_\.\-]?[a-zA-Z0-9]+)*)@([A-Za-z0-9]+)(([\.\-]?[a-zA-Z0-9]+)*)\.([A-Za-z]{2,})", RegexOptions.IgnoreCase);
        }

        #endregion 判断字符串是否包含邮箱地址

        #region 判断字符串是否是网址

        /// <summary>
        /// 判断字符串是否是网址
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static bool IsUrl(this string source)
        {
            if (source.IsNullOrEmpty()) return false;
            return Regex.IsMatch(source, @"^(((file|gopher|news|nntp|telnet|http|ftp|https|ftps|sftp)://)|(www\.))+(([a-zA-Z0-9\._-]+\.[a-zA-Z]{2,6})|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(/[a-zA-Z0-9\&amp;%_\./-~-]*)?$", RegexOptions.IgnoreCase);
        }

        #endregion 判断字符串是否是网址

        #region 判断字符串是否包含网址

        /// <summary>
        /// 判断字符串是否包含网址
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static bool HasUrl(this string source)
        {
            if (source.IsNullOrEmpty()) return false;
            return Regex.IsMatch(source, @"(((file|gopher|news|nntp|telnet|http|ftp|https|ftps|sftp)://)|(www\.))+(([a-zA-Z0-9\._-]+\.[a-zA-Z]{2,6})|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(/[a-zA-Z0-9\&amp;%_\./-~-]*)?", RegexOptions.IgnoreCase);
        }

        #endregion 判断字符串是否包含网址

        #region 判断字符串是否是手机号

        /// <summary>
        /// 判断字符串是否是手机号
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static bool IsMobile(this string source)
        {
            if (source.IsNullOrEmpty()) return false;
            return Regex.IsMatch(source, @"^1[35]\d{9}$", RegexOptions.IgnoreCase);
        }

        #endregion 判断字符串是否是手机号

        #region 判断字符串是否包含手机号

        /// <summary>
        /// 判断字符串是否包含手机号
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static bool HasMobile(this string source)
        {
            if (source.IsNullOrEmpty()) return false;
            return Regex.IsMatch(source, @"1[35]\d{9}", RegexOptions.IgnoreCase);
        }

        #endregion 判断字符串是否包含手机号

        #region 判断字符串是不是中国电话

        /// <summary>
        /// 判断字符串是不是中国电话
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        /// <remarks>
        /// 格式010-85849685
        /// </remarks>
        public static bool IsChinaPhoneNumber(this string source)
        {
            if (source.IsNullOrEmpty()) return false;
            return Regex.IsMatch(source, @"^\d{3,4}-?\d{6,8}$", RegexOptions.IgnoreCase);
        }

        #endregion 判断字符串是不是中国电话

        #region 判断字符串是否是IP

        /// <summary>
        /// 判断字符串是否是IP
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static bool IsIP(this string source)
        {
            if (source.IsNullOrEmpty()) return false;
            return Regex.IsMatch(source, @"^(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9])\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[0-9])$", RegexOptions.IgnoreCase);
        }

        #endregion 判断字符串是否是IP

        #region 判断字符串是否包含IP

        /// <summary>
        /// 判断字符串是否包含IP
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static bool HasIP(this string source)
        {
            if (source.IsNullOrEmpty()) return false;
            return Regex.IsMatch(source, @"(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9])\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[0-9])", RegexOptions.IgnoreCase);
        }

        #endregion 判断字符串是否包含IP

        #region 判断字符串是否是身份证号

        /*********************************** 身份证验证开始 ****************************************/
        /**
         * 身份证号码验证 1、号码的结构 公民身份号码是特征组合码，由十七位数字本体码和一位校验码组成。排列顺序从左至右依次为：六位数字地址码，
         * 八位数字出生日期码，三位数字顺序码和一位数字校验码。 2、地址码(前六位数）
         * 表示编码对象常住户口所在县(市、旗、区)的行政区划代码，按GB/T2260的规定执行。 3、出生日期码（第七位至十四位）
         * 表示编码对象出生的年、月、日，按GB/T7408的规定执行，年、月、日代码之间不用分隔符。 4、顺序码（第十五位至十七位）
         * 表示在同一地址码所标识的区域范围内，对同年、同月、同日出生的人编定的顺序号， 顺序码的奇数分配给男性，偶数分配给女性。 5、校验码（第十八位数）
         * （1）十七位数字本体码加权求和公式 S = Sum(Ai * Wi), i = 0, ... , 16 ，先对前17位数字的权求和
         * Ai:表示第i位置上的身份证号码数字值 Wi:表示第i位置上的加权因子 Wi: 7 9 10 5 8 4 2 1 6 3 7 9 10 5 8 4 2
         * （2）计算模 Y = mod(S, 11) （3）通过模得到对应的校验码 Y: 0 1 2 3 4 5 6 7 8 9 10 校验码: 1 0 X 9 8 7 6 5 4 3 2
         */

        /// <summary>
        /// 判断字符串是否是身份证
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static bool IsIDCard(this string source)
        {
            if (source.IsNullOrEmpty()) return false;
            if (source.Length == 18)
            {
                bool check = IsIDCard18(source);
                return check;
            }
            else if (source.Length == 15)
            {
                bool check = IsIDCard15(source);
                return check;
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// 身份证省份是否正确
        /// </summary>
        /// <param name="source"></param>
        public static void CheckProvince(int provinceId)
        {
            Dictionary<int, string> cityNumbers = new Dictionary<int, string>();
            cityNumbers.Add(11, "北京");
            cityNumbers.Add(12, "天津");
            cityNumbers.Add(13, "河北");
            cityNumbers.Add(14, "山西");
            cityNumbers.Add(15, "内蒙古");
            cityNumbers.Add(21, "辽宁");
            cityNumbers.Add(22, "吉林");
            cityNumbers.Add(23, "黑龙江");
            cityNumbers.Add(31, "上海");
            cityNumbers.Add(32, "江苏");
            cityNumbers.Add(33, "浙江");
            cityNumbers.Add(34, "安徽");
            cityNumbers.Add(35, "福建");
            cityNumbers.Add(36, "江西");
            cityNumbers.Add(37, "山东");
            cityNumbers.Add(41, "河南");
            cityNumbers.Add(42, "湖北");
            cityNumbers.Add(43, "湖南");
            cityNumbers.Add(44, "广东");
            cityNumbers.Add(45, "广西");
            cityNumbers.Add(46, "海南");
            cityNumbers.Add(50, "重庆");
            cityNumbers.Add(51, "四川");
            cityNumbers.Add(52, "贵州");
            cityNumbers.Add(53, "云南");
            cityNumbers.Add(54, "西藏");
            cityNumbers.Add(61, "陕西");
            cityNumbers.Add(62, "甘肃");
            cityNumbers.Add(63, "青海");
            cityNumbers.Add(64, "宁夏");
            cityNumbers.Add(65, "新疆");
            cityNumbers.Add(71, "台湾");
            cityNumbers.Add(81, "香港");
            cityNumbers.Add(82, "澳门");
            cityNumbers.Add(91, "国外");
        }

        /// <summary>
        /// 判断字符串是否是十八位身份证号码
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static bool IsIDCard18(string source)
        {
            if (source.IsNullOrEmpty()) return false;
            long n = 0;
            if (long.TryParse(source.Remove(17), out n) == false || n < Math.Pow(10, 16) || long.TryParse(source.Replace('x', '0').Replace('X', '0'), out n) == false)
            {
                return false;//数字判断
            }
            string address = "11x22x35x44x53x12x23x36x45x54x13x31x37x46x61x14x32x41x50x62x15x33x42x51x63x21x34x43x52x64x65x71x81x82x91";
            if (address.IndexOf(source.Remove(2)) == -1)
            {
                return false;//省份判断
            }
            string birth = source.Substring(6, 8).Insert(6, "-").Insert(4, "-");
            DateTime time = new DateTime();
            if (DateTime.TryParse(birth, out time) == false)
            {
                return false;//生日判断
            }
            string[] arrVarifyCode = ("1,0,x,9,8,7,6,5,4,3,2").Split(',');
            string[] Wi = ("7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2").Split(',');
            char[] Ai = source.Remove(17).ToCharArray();
            int sum = 0;
            for (int i = 0; i < 17; i++)
            {
                sum += int.Parse(Wi[i]) * int.Parse(Ai[i].ToString());
            }
            int y = -1;
            Math.DivRem(sum, 11, out y);
            if (arrVarifyCode[y] != source.Substring(17, 1).ToLower())
            {
                return false;//校验码判断
            }
            return true;//符合GB11643-1999标准
        }

        /// <summary>
        /// 判断字符串是否是十五位身份证号
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static bool IsIDCard15(string source)
        {
            if (source.IsNullOrEmpty()) return false;
            long n = 0;
            if (long.TryParse(source, out n) == false || n < Math.Pow(10, 14))
            {
                return false;//数字判断
            }
            string address = "11x22x35x44x53x12x23x36x45x54x13x31x37x46x61x14x32x41x50x62x15x33x42x51x63x21x34x43x52x64x65x71x81x82x91";
            if (address.IndexOf(source.Remove(2)) == -1)
            {
                return false;//省份判断
            }
            string birth = source.Substring(6, 6).Insert(4, "-").Insert(2, "-");
            DateTime time = new DateTime();
            if (DateTime.TryParse(birth, out time) == false)
            {
                return false;//生日判断
            }
            return true;//符合15位身份证标准
        }

        #endregion 判断字符串是否是身份证号

        #region 判断字符串长度是不是在限定数之间

        /// <summary>
        /// 判断字符串长度是不是在限定数之间
        /// </summary>
        /// <param name="source">字符串</param>
        /// <param name="low">大于等于</param>
        /// <param name="high">小于等于</param>
        /// <returns></returns>
        /// <remarks>
        /// 一个中文为两个字符
        /// </remarks>
        public static bool IsLimitLength(this string source, int low, int high)
        {
            if (source.IsNullOrEmpty()) return false;
            int length = Regex.Replace(source, @"[^\x00-\xff]", "OK").Length;
            if ((length <= low) && (length >= high))
            {
                return false;
            }
            return true;
        }

        #endregion 判断字符串长度是不是在限定数之间
  

        #region 判断字符串是否是邮政编码

        /// <summary>
        /// 判断字符串是否是邮政编码
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        /// <remarks>
        /// 6个数字
        /// </remarks>
        public static bool IsPostCode(this string source)
        {
            if (source.IsNullOrEmpty()) return false;
            return Regex.IsMatch(source, @"^\d{6}$", RegexOptions.IgnoreCase);
        }

        #endregion 判断字符串是否是邮政编码

        #region 判断字符串是否是中文

        /// <summary>
        /// 判断字符串是否是中文
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static bool IsChinese(this string source)
        {
            if (source.IsNullOrEmpty()) return false;
            return Regex.IsMatch(source, @"^[\u4e00-\u9fa5]+$", RegexOptions.IgnoreCase);
        }

        #endregion 判断字符串是否是中文

        #region 判断字符串是否包含中文

        /// <summary>
        /// 判断字符串是否包含中文
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static bool HasChinese(this string source)
        {
            if (source.IsNullOrEmpty()) return false;
            return Regex.IsMatch(source, @"[\u4e00-\u9fa5]+", RegexOptions.IgnoreCase);
        }

        #endregion 判断字符串是否包含中文

        #region 判断字符串是否是常规字符组合

        /// <summary>
        /// 判断字符串是否是常规字符组合
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        /// <remarks>
        /// 只包含字母，数字，下划线的组合
        /// </remarks>
        public static bool IsNormalChar(this string source)
        {
            if (source.IsNullOrEmpty()) return false;
            return Regex.IsMatch(source, @"[\w\d_]+", RegexOptions.IgnoreCase);
        }

        #endregion 判断字符串是否是常规字符组合

        #region 判断字符串是否是数字

        /// <summary>
        /// 判断字符串是否是数字
        /// </summary>
        /// <param name="source">输入字符</param>
        /// <returns>返回一个bool类型的值</returns>
        public static bool IsNumber(this string source)
        {
            if (source.IsNullOrEmpty()) return false;
            return Regex.IsMatch(source, "^([0]|([1-9]+\\d{0,}?))(.[\\d]+)?$");
        }

        #endregion 判断字符串是否是数字

        #region 判断字符串是否是日期型
        /// <summary>
        /// 验证日期
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static bool IsDateTime(this string source)
        {
            try
            {
                DateTime time = Convert.ToDateTime(source);
                return true;
            }
            catch
            {
                return false;
            }
        }
        #endregion
        #endregion 判断

        #region 转义

        #region 将字符串转义为表达式

        /// <summary>
        /// 将字符串转义为表达式
        /// </summary>
        /// <param name="value">要转换的字符串</param>
        /// <returns>转义后的结果</returns>
        public static string EncodeToJsExpression(this string source)
        {
            if (source.IsNullOrEmpty()) return string.Empty;
            return Regex.Replace(source, @"(\r|\n|'|""|\\|/)", (s) =>
            {
                if (s.Value == "\r") return "\\r";
                if (s.Value == "\n") return "\\n";
                return string.Concat("\\", s.Value);
            });
        }

        #endregion 将字符串转义为表达式

        #region 将字符串从JS的转义中转换

        /// <summary>
        /// 将字符串从JS的转义中转换
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static string DecodeFromJsExpression(this string source)
        {
            if (source.IsNullOrEmpty()) return string.Empty;

            return Regex.Replace(source, @"\\(r|n|'|""|\|/|u([a-fA-F0-9]{4}))", (s) =>
             {
                 var key = s.Groups[1].Value;

                 if (key == "r") return "\r";
                 if (key == "n") return "\n";
                 if (key[0] == 'u')
                 {
                     return ((char)s.Groups[2].Value.ToInt32(style: NumberStyles.AllowHexSpecifier)).ToString();
                 }
                 return key;
             });
        }

        #endregion 将字符串从JS的转义中转换

        #endregion 转义

        #region 拆分

        #region 对字符串进行正则表达式匹配，并返回所有匹配的字符串数组

        /// <summary>
        /// 对字符串进行正则表达式匹配，并返回所有匹配的字符串数组
        /// </summary>
        /// <param name="source">字符串</param>
        /// <param name="pattern">正则表达式模式</param>
        /// <param name="options">选项</param>
        /// <returns>如果匹配失败，则返回false</returns>
        public static string[] RegMatch(this string source, string pattern, RegexOptions options = RegexOptions.IgnoreCase)
        {
            var m = Regex.Match(source, pattern, options);
            if (!m.Success) return null;

            return m.Groups.Cast<Group>().Select(s => s.Value).ToArray();
        }

        #endregion 对字符串进行正则表达式匹配，并返回所有匹配的字符串数组

        #region 对字符串进行正则表达式匹配，并返回所有匹配的字符串数组的字符组

        /// <summary>
        /// 对字符串进行正则表达式匹配，并返回所有匹配的字符串数组
        /// </summary>
        /// <param name="source">字符串</param>
        /// <param name="pattern">正则表达式模式</param>
        /// <param name="options">选项</param>
        /// <returns>如果匹配失败，则返回false</returns>
        public static List<string[]> RegMatches(this string source, string pattern, RegexOptions options = RegexOptions.IgnoreCase)
        {
            var m = Regex.Matches(source, pattern, options);

            return m.Cast<Match>().Select(s => s.Groups.Cast<Group>().Select(x => x.Value).ToArray()).ToList();
        }

        #endregion 对字符串进行正则表达式匹配，并返回所有匹配的字符串数组的字符组

        #region 将HEX字符串转换为对应的字节数组

        /// <summary>
        /// 将HEX字符串转换为对应的字节数组
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static byte[] ConvertHexStringToBytes(this string source)
        {
            if (source.IsNullOrEmpty()) return new byte[0];

            var matches = Regex.Matches(source, @"[a-f\d]{2}", RegexOptions.IgnoreCase);
            return matches.Cast<Match>().Select(s => (byte)((s.Value[0].ToHexByte() << 4) + s.Value[0].ToHexByte())).ToArray();
        }

        #endregion 将HEX字符串转换为对应的字节数组

        #region 将字符串进行正则表达式分隔,并返回所有匹配的字符串数组

        /// <summary>
        /// 对字符串进行正则表达式匹配，并返回所有匹配的字符串数组
        /// </summary>
        /// <param name="source">字符串</param>
        /// <param name="pattern">正则表达式模式</param>
        /// <param name="options">选项</param>
        /// <returns>如果匹配失败，则返回false</returns>
        public static int[] RegSplitAsIntArray(this string source, string pattern, RegexOptions options = RegexOptions.IgnoreCase)
        {
            return Regex.Split(source, pattern, options).Select(s => s.ToInt32()).ToArray();
        }

        #endregion 将字符串进行正则表达式分隔,并返回所有匹配的字符串数组

        #endregion 拆分
    }
}